// Driver Selector module
// TreeView Version by HyperYagami
// Tab control by regret

/* changelog:
 update 10: improve game icon display
 update  9: add game icon display
 update  8: improved show rom info dialog
 update  7: change tree list make method (faster)
 update  6: update hardware selection
 update  5: add filter and source cleanup
 update  4: add tab control for preview image
 update  3: add tab control for options
 update  2: add icon display
 update  1: modify options layout (ref: fba plus)
*/

#include "burner.h"
#include <shellapi.h>
#include "imageload+.h"
#include "imageresize.h"
#include "iconload.h"
#include "seldef.h"
#include "favorites.h"

// Disable selecting non-available sets
#define DISABLE_NON_AVAILABLE_SELECT 0
// Prompt user on loading non-working sets
#define NON_WORKING_PROMPT_ON_LOAD 1

// use megadrive driver list
//#define MD_DRIVER_LIST

// ==> timer, added by regret
#define IMAGE_TIMER_ID 1
#define IMAGE_TIMER 2500
#define FILTER_TIMER_ID 2
#define FILTER_TIMER 500

UINT_PTR imageTimer = 0;
UINT_PTR filterTimer = 0;
// <== timer

HWND hSelDlg = NULL;
static HWND hSelList = NULL;
static int dialogSelect = -1; // The driver which this dialog selected
static int lastSelect = -1;

// Info-box
enum INFO_CONTENT { INFO_ROMNAME = 0, INFO_ROMINFO, INFO_SYSTEM, INFO_COMMENT, INFO_NOTE, INFO_NUM };

static HWND hInfoLabel[INFO_NUM] = { NULL };
static HWND hInfoText[INFO_NUM] = { NULL };
static HBRUSH whiteBGBrush = CreateSolidBrush(RGB(0xFF,0xFF,0xFF));

struct InfoDefine {
	unsigned int label;
	unsigned int text;
} infoDef[] = {
	{ IDC_LABELROMNAME, IDC_TEXTROMNAME },
	{ IDC_LABELROMINFO, IDC_TEXTROMINFO },
	{ IDC_LABELSYSTEM, IDC_TEXTSYSTEM },
	{ IDC_LABELCOMMENT, IDC_TEXTCOMMENT },
	{ IDC_LABELNOTES, IDC_TEXTNOTES },
};

static HBITMAP hPreview = NULL;
static HBITMAP hDefPreview = NULL;
static HICON hExpand, hCollapse;
static HICON hNotWorking, hNotFoundEss, hNotFoundNonEss, hROMICO, hClone, hImperfect;

static bool treeBuilding = false; // if 1, ignore TVN_SELCHANGED messages

// ==> System define
int loadSystemSel = 0;
struct SystemDefine {
	int hardwareCode;
	int buttonCode;
	int hardware;
} systemDef[] = {
	{ IDS_SEL_HARDWARE, 	-1,				-1	 			},
	{ IDS_SEL_CAVE,			IDC_CAVE,		MASKCAVE		},
	{ IDS_SEL_CPS1,			IDC_CPS1,		MASKCPS			},
	{ IDS_SEL_CPS2,			IDC_CPS2,		MASKCPS2		},
	{ IDS_SEL_CPS3,			IDC_CPS3,		MASKCPS3		},
	{ IDS_SEL_GALAXIAN,		IDC_GALAXIAN,	MASKGALAXIAN	},
	{ IDS_SEL_KANEKO16,		IDC_KANEKO16,	MASKKANEKO16	},
	{ IDS_SEL_KONAMI,		IDC_KONAMI,		MASKKONAMI		},
	{ IDS_SEL_NEOGEO,		IDC_NEOGEO,		MASKNEOGEO		},
	{ IDS_SEL_PACMAN,		IDC_PACMAN,		MASKPACMAN		},
	{ IDS_SEL_PGM,			IDC_PGM,		MASKPGM			},
	{ IDS_SEL_PSIKYO,		IDC_PSIKYO,		MASKPSIKYO		},
	{ IDS_SEL_SEGA,			IDC_SEGA,		MASKSEGA		},
	{ IDS_SEL_TOAPLAN,		IDC_TOAPLAN,	MASKTOAPLAN		},
	{ IDS_SEL_TAITO,		IDC_TAITO,		MASKTAITO		},
	{ IDS_SEL_ATARI,		IDC_ATARI,		MASKATARI		},
	{ IDS_SEL_MISC,			IDC_DRVMISC,	MASKMISC		},
#ifdef MD_DRIVER_LIST
	{ IDS_SEL_MEGADRIVE,	IDC_MEGADRIVE,	MASKMD			},
#endif
	0
};
// <== System define

int loadMenuShowX = 0;
int loadDriverShowX = 0;

static bool rightClick = false;

// ==> tab control, added by regret
enum eTab {
	NONE = -1,
	LIST,
	FILTER,
//	ETC,
	MAX_TAB,
};

enum eImageTab {
	IMG_NONE = -1,
	IMG_PREVIEW,
	IMG_TITLE,
	IMG_FLYER,
	IMG_SCORE,
	IMG_SELECT,
	IMG_GAMEOVER,
	IMG_BOSS,
	IMG_MAXTAB,
};

struct ImageDefine {
	int imageId;
	int pathId;
} imageDef[] = {
	{ IMG_PREVIEW, PATH_PREVIEW },
	{ IMG_TITLE, PATH_TITLE },
	{ IMG_FLYER, PATH_FLYER },
	{ IMG_SCORE, PATH_SCORE },
	{ IMG_SELECT, PATH_SELECT },
	{ IMG_GAMEOVER, PATH_GAMEOVER },
	{ IMG_BOSS, PATH_BOSS },
	-1
};

HWND hSelTab = NULL;
HWND hImgTab = NULL;
int nCurrentTab, nCurImgTab;
int loadTabSel;

static int previewOrgX;
static int previewOrgY;

// for tab control theme, thanks MAME32 team
FARPROC fnIsThemed  = NULL;
HBRUSH hTabBkBrush = NULL;
bool themeActive = false;

// filter string
wchar_t userFilterStr[MAX_PATH];
static wchar_t szFilterStr[64];
static bool hasFilterStr = false;

static const wchar_t szShowUnAvail[16] = L"lamer";
static bool showUnAvail = false;
// <== tab control

Favorites* favorites = NULL; // favorite filter, added by regret

static wchar_t ResDir[] = L"res\\"; // custom resource dir
static wchar_t archiveName[64]; // preview archive name

struct NODEINFO {
	bool used;
	int driverId;
	bool isParent;
	HTREEITEM hTreeHandle;
	int iconid;
};

static map<string, NODEINFO*> driverNode;
static unsigned int nodeCount;
static unsigned int cloneCount; // clone game count, added by regret

// icon
static HTREEITEM hIconEffectItem = NULL;
static HBITMAP effectBgBitmap = NULL;

static void rebuildEverything();

// Check if a specified driver is working
static inline bool checkWorkingStatus(int driver)
{
	return (BurnDrvGetFlags(driver) & BDF_GAME_WORKING);
}

// ==> game list filter, added by regret
static inline bool myStrStrI(const wchar_t* pszStr, const wchar_t* pszSrch)
{
	if (!pszStr || !pszSrch) {
		return NULL; // no content
	}

	wchar_t* cp = (wchar_t*)pszStr;
	wchar_t* s1;
	wchar_t* s2;

	while (*cp) {
		s1 = cp;
		s2 = (wchar_t*)pszSrch;

		while (*s1 && *s2 && !_wcsnicmp(s1, s2, 1)) {
			s1++, s2++;
		}

		if (!*s2) {
			return true;//cp;
		}

		cp++;
	}
	return false;
}

static void initFilterString()
{
	SendDlgItemMessage(hSelDlg, IDC_FILTER_KEY, CB_ADDSTRING, 0, (LPARAM)L"bootleg");
	SendDlgItemMessage(hSelDlg, IDC_FILTER_KEY, CB_ADDSTRING, 0, (LPARAM)L"decrypted");
	SendDlgItemMessage(hSelDlg, IDC_FILTER_KEY, CB_ADDSTRING, 0, (LPARAM)L"street fighter");
	SendDlgItemMessage(hSelDlg, IDC_FILTER_KEY, CB_ADDSTRING, 0, (LPARAM)L"king of fighters");

	// init user filter string
	if (wcslen(userFilterStr) > 0) {
		wchar_t szToken[MAX_PATH];
		wcscpy(szToken, userFilterStr);
		wchar_t* token = wcstok(szToken, L";");
		while (token != NULL) {
			SendDlgItemMessage(hSelDlg, IDC_FILTER_KEY, CB_ADDSTRING, 0, (LPARAM)token);
			token = wcstok(NULL, L";");
		}
		free(token);
	}

//	wcscpy(szFilterStr, L""));
	SendDlgItemMessage(hSelDlg, IDC_FILTER_KEY, WM_SETTEXT, (WPARAM)sizearray(szFilterStr), (LPARAM)szFilterStr);
	hasFilterStr = wcslen(szFilterStr) > 0 ? true : false;
}

static void initFilterSystem()
{
	for (int i = 0; systemDef[i].hardwareCode; i++) {
		SendDlgItemMessage(hSelDlg, IDC_FILTER_SYSTEM, CB_ADDSTRING, 0, (LPARAM)localiseStringEx(systemDef[i].hardwareCode));
	}
	SendDlgItemMessage(hSelDlg, IDC_FILTER_SYSTEM, CB_SETCURSEL, (WPARAM)loadSystemSel, 0);
}

static bool gameInfoFiltered(wchar_t* pszFilter, unsigned index)
{
	// instead "_tcsstr"
	if (myStrStrI(AtoW(BurnDrvGetTextA(DRV_NAME, index)), pszFilter)
		|| myStrStrI(transGameName(BurnDrvGetTextA(DRV_FULLNAME, index), index), pszFilter)
		|| myStrStrI(AtoW(BurnDrvGetTextA(DRV_MANUFACTURER, index)), pszFilter)
		|| myStrStrI(AtoW(BurnDrvGetTextA(DRV_DATE, index)), pszFilter)
	) {
		// match the filter string
		return false;
	}

	// unicode full name
	wchar_t* pszName = AtoW(BurnDrvGetTextA(DRV_FULLNAME, index));
	if (myStrStrI(pszName, pszFilter)) {
		return false;
	}

	// comment
	if (myStrStrI(AtoW(BurnDrvGetTextA(DRV_COMMENT, index)), pszFilter)) {
		return false;
	}

	// not found
	return true;
}

static inline bool systemFiltered(unsigned index)
{
	if (loadSystemSel == 0)
		return false; // any

	const int nHardware = 1 << (BurnDrvGetHardwareCode(index) >> 24);

	if (systemDef[loadSystemSel].hardware == nHardware) {
		return false;
	}
	return true;
}

static bool isGameFiltered(bool showUnAvail, unsigned index)
{
	if (showUnAvail) {
		// show unavailable games
		if (auditState->get(index) != AuditState::AUDIT_FAIL) {
			return true;
		}
	}
	else if (systemFiltered(index)) {
		return true;
	}
	else if (hasFilterStr && gameInfoFiltered(szFilterStr, index)) {
		return true;
	}

	return false;
}

static inline bool gameListFilter(bool isParent, unsigned index)
{
//	if (BurnDrvGetFlags(index) & BDF_BOARDROM) {
//		return TRUE;
//	}

	if (isParent) {
		// Skip clones
		if (BurnDrvGetTextA(DRV_PARENT, index) && (BurnDrvGetFlags(index) & BDF_CLONE)) {
			return true;
		}
	} else {
		// Skip parents
		if (!BurnDrvGetTextA(DRV_PARENT, index) || !(BurnDrvGetFlags(index) & BDF_CLONE)) {
			return true;
		}
	}

	// Skip non-available games if needed
	if (avOk && (loadMenuShowX & AVAILONLY)
		&& auditState->get(index) == AuditState::AUDIT_FAIL
		&& !showUnAvail) {
		return true;
	}

	int nHardware = 1 << (BurnDrvGetHardwareCode(index) >> 24);
	if ((nHardware & MASKALL) && (nHardware & loadDriverShowX)) {
		return true;
	}

	if (isGameFiltered(showUnAvail, index)) {
		return true;
	}

	// ==> favorite filter, added by regret
	if ((loadMenuShowX & SHOWFAVORITE) && favorites->filter(index)) {
		return true;
	}
	// <== favorite filter

	return false;
}

// do sth after filter change
static void filterChange()
{
	rebuildEverything();
}
// <== game list filter

static bool checkImperfectStatus(int driver)
{
	const wchar_t* comment = AtoW(BurnDrvGetTextA(DRV_COMMENT, driver));
	if (!comment)
		return false;

	if (myStrStrI(comment, L"imperfect")
		|| myStrStrI(comment, L"incomplete")
		|| myStrStrI(comment, L"no sound")
		|| myStrStrI(comment, L"missing")
		|| myStrStrI(comment, L"preliminary")
		|| myStrStrI(comment, L"bad dump")) {
		return true;
	}
	return false;
}

// ==> load custom resource, added by regret
struct CustomIcon {
	HICON* pIcon;
	wchar_t* pszResName;
} sCusIco[] = {
	{ &hExpand,			L"Plus.ico"						},
	{ &hCollapse,		L"Minus.ico"					},
	{ &hNotWorking,		L"Not Working.ico"				},
	{ &hNotFoundEss,	L"Not Found.ico"				},
	{ &hNotFoundNonEss,	L"Not Found Non Essential.ico"	},
	{ &hROMICO,			L"ROM.ico"						},
	{ &hClone,			L"Clone.ico"					},
	{ &hImperfect,		L"Imperfect.ico"				},
	NULL // end
};

static void loadCustomRes()
{
	wchar_t tmpStr[MAX_PATH];

	swprintf(tmpStr, L"%s%s", ResDir, L"preview.bmp");
	if ((hDefPreview = (HBITMAP)LoadImage(hAppInst, tmpStr, IMAGE_BITMAP, 304, 224, LR_LOADFROMFILE)) == NULL)
		hDefPreview = LoadBitmap(hAppInst, MAKEINTRESOURCE(BMP_PREVIEW));

	for (int i = 0; sCusIco[i].pIcon; i++) {
		swprintf(tmpStr, L"%s%s", ResDir, sCusIco[i].pszResName);
		if ((*sCusIco[i].pIcon = (HICON)LoadImage(hAppInst, tmpStr, IMAGE_ICON, 16, 16, LR_LOADFROMFILE)) == NULL)
			*sCusIco[i].pIcon = (HICON)LoadImage(hAppInst, MAKEINTRESOURCE(IDI_TV_PLUS + i), IMAGE_ICON, 16, 16, LR_DEFAULTCOLOR);
	}

	effectBgBitmap = LoadBitmap(hAppInst, MAKEINTRESOURCE(BMP_ICONBG));
}

static void destroyResource()
{
	if (hPreview) {
		DeleteObject((HGDIOBJ)hPreview);
		hPreview = NULL;
	}
	if (hDefPreview) {
		DeleteObject((HGDIOBJ)hDefPreview);
		hDefPreview = NULL;
	}

	for (int i = 0; sCusIco[i].pIcon; i++) {
		if (*sCusIco[i].pIcon) {
			DestroyIcon(*sCusIco[i].pIcon);
			*sCusIco[i].pIcon = NULL;
		}
	}
}

static void InvalidateLargeIcon()
{
	if (!hSelDlg)
		return;

	RECT rect;
	GetWindowRect(GetDlgItem(hSelDlg, IDC_SCREENSHOT_V), &rect);
	int w = rect.right - rect.left;
	int h = rect.bottom - rect.top;
	POINT pt;
	pt.x = rect.left; pt.y = rect.top;
	ScreenToClient(hSelDlg, &pt);

	rect.left = pt.x;
	rect.top = pt.y - 16;
	rect.right = rect.left + w;
	rect.bottom = rect.top + h + 16;

	InvalidateRect(hSelDlg, &rect, TRUE);
}

static void moveLargeIconPos(int x, int y)
{
	RECT rc;
	rc.left = x; rc.top = y;
	MapWindowPoints(hSelList, hSelDlg, (LPPOINT)(&rc), 2);

	SetWindowPos(GetDlgItem(hSelDlg, IDC_SCREENSHOT_V), 0,
			rc.left, rc.top, 0, 0,
			SWP_NOSIZE | SWP_NOSENDCHANGING);
}

static inline int getDrawIconsOffset(TVITEMEX& TvItem)
{
	int offset = 0;

	const NODEINFO* nodeInfo = (NODEINFO*)TvItem.lParam;
	const bool isParent = nodeInfo->isParent;
	const bool noIcon = (loadMenuShowX & NOICON) ? true : false;

	// Draw plus and minus buttons
	offset += 24;

	// Add by Sho, clone game moves right 2 char
	if (!isParent) {
		offset += 12;
	}

	// Display the short name if needed
	if (loadMenuShowX & SHOWSHORT) {
		offset += 68;
	}

	// Draw game icons
	if (!noIcon) {
		if (nodeInfo->iconid >= 0) {
			if (hIconEffectItem == TvItem.hItem) {
				offset += 16;
			}
		}
		offset += 20;
	}

	return offset;
}

static inline void drawIcons(TVITEMEX& TvItem, RECT& rect, HDC hdc, unsigned index)
{
	const NODEINFO* nodeInfo = (NODEINFO*)TvItem.lParam;
	const int drvIndex = nodeInfo->driverId;
	const bool isParent = nodeInfo->isParent;
	const bool noIcon = (loadMenuShowX & NOICON) ? true : false;

	// Draw plus and minus buttons
	if (isParent) {
		if (TvItem.state & TVIS_EXPANDED) {
			DrawIconEx(hdc, rect.left + 4, rect.top, hCollapse, 16, 16, 0, NULL, DI_NORMAL);
		} else {
			if (TvItem.cChildren) {
				DrawIconEx(hdc, rect.left + 4, rect.top, hExpand, 16, 16, 0, NULL, DI_NORMAL);
			}
		}
	}
	rect.left += 24;

	// Add by Sho, clone game moves right 2 char
	if (!isParent) {
		rect.left += 12;
	}

	// Display the short name if needed
	if (loadMenuShowX & SHOWSHORT) {
		DrawText(hdc, AtoW(BurnDrvGetTextA(DRV_NAME, index)), -1, &rect, DT_NOPREFIX | DT_SINGLELINE | DT_LEFT | DT_VCENTER | DT_NOCLIP);
		rect.left += 68;
	}

	// Draw game icons
	if (!noIcon) {
		if (nodeInfo->iconid >= 0) {
			if (hIconEffectItem == TvItem.hItem) {
				SendDlgItemMessage(hSelDlg, IDC_SCREENSHOT_V, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)effectBgBitmap);
				HDC hdc = GetDC(GetDlgItem(hSelDlg, IDC_SCREENSHOT_V));
				DrawIconEx(hdc, 3, 3, getIconFromImageList(true, nodeInfo->iconid), 32, 32, 0, NULL, DI_NORMAL);
				moveLargeIconPos(rect.left, rect.top);
				rect.left += 16;
			} else {
				drawIcon(false, nodeInfo->iconid, hdc, rect.left, rect.top);
			}
		}
		else if (!checkWorkingStatus(drvIndex)) {
			DrawIconEx(hdc, rect.left, rect.top, hNotWorking, 16, 16, 0, NULL, DI_NORMAL);
		}
		else if (auditState->get(drvIndex) == AuditState::AUDIT_FAIL) {
			DrawIconEx(hdc, rect.left, rect.top, hNotFoundEss, 16, 16, 0, NULL, DI_NORMAL);
		}
		else if (auditState->get(drvIndex) == AuditState::AUDIT_PARTPASS) {
			DrawIconEx(hdc, rect.left, rect.top, hNotFoundNonEss, 16, 16, 0, NULL, DI_NORMAL);
		}
		else if (checkImperfectStatus(drvIndex)) {
			DrawIconEx(hdc, rect.left, rect.top, hImperfect, 16, 16, 0, NULL, DI_NORMAL);
		}
		else if (!isParent) {
			DrawIconEx(hdc, rect.left, rect.top, hClone, 16, 16, 0, NULL, DI_NORMAL);
		}
		else {
			DrawIconEx(hdc, rect.left, rect.top, hROMICO, 16, 16, 0, NULL, DI_NORMAL);
		}
		rect.left += 20;
	}
}
// <== load custom resource

static void killImageTimer()
{
	if (imageTimer) {
		KillTimer(hSelDlg, imageTimer);
		imageTimer = 0;
	}
}

static void killFilterTimer()
{
	if (filterTimer) {
		KillTimer(hSelDlg, filterTimer);
		filterTimer = 0;
	}
}

static void myEndDialog(bool ok)
{
	killImageTimer();
	killFilterTimer();

	SendDlgItemMessage(hSelDlg, IDC_SCREENSHOT_H, STM_SETIMAGE, IMAGE_BITMAP, 0);

	// ==> load custom resource, modified by regret
	destroyResource();
	// <== load custom resource

	// ==> favorite filter, added by regret
	favorites->save();
	// <== favorite filter

	lastSelect = dialogSelect;
	if (!ok) {
		dialogSelect = -1;
	}

	// start game
	if (dialogSelect >= 0) {
		if (fullscreenOnStart) {
			vidFullscreen = 1;
		}
		BurnerDrvInit(dialogSelect, true); // Init the game driver
		menuSync(MENUT_ALL);
	} else {
		gameInpCheckMouse();
	}
}

// User clicked ok for a driver in the list
static void selOkay()
{
	// modified by emufan
	unsigned int selectedGame = dialogSelect;
	if (!rightClick) {
		TVITEMEX TvItem;
		HTREEITEM hSelectHandle = TreeView_GetNextItem(hSelList, ~0U, TVGN_CARET);
		if (!hSelectHandle) {		// Nothing is selected, return without closing the window
			return;
		}

		TvItem.hItem = hSelectHandle;
		TvItem.mask = TVIF_PARAM;
		TreeView_GetItem(hSelList, &TvItem);
		if ((NODEINFO*)TvItem.lParam) {
			selectedGame = ((NODEINFO*)TvItem.lParam)->driverId;
		}
	} else {
		rightClick = false;
	}

#if DISABLE_NON_AVAILABLE_SELECT
	// Game not available, return without closing the window
	if (getAuditState(selectedGame) == AUDIT_FAIL) {
		return;
	}
#endif

#if NON_WORKING_PROMPT_ON_LOAD
	if (!checkWorkingStatus(selectedGame)) {
		if (MessageBox(hSelDlg, localiseStringEx(IDS_SEL_LOADNONWORK),
				L"Warning!", MB_YESNO | MB_DEFBUTTON2 | MB_ICONWARNING) == IDNO) {
			return;
		}
	}
#endif

	dialogSelect = selectedGame;

	myEndDialog(true);
}

// ==> icon load, added by regret
static HANDLE iconThread;

bool gameInList(const char* name)
{
	if (!name) {
		return false;
	}

	map<string, NODEINFO*>::iterator iter = driverNode.find(name);
	if (iter != driverNode.end()) {
		return true;
	}
	return false;
}

static unsigned __stdcall iconThreadEntry(void* param)
{
	// create image list
	if (createIconList()) {
		clearIcons();
		return 1;
	}

	// check archive file
	char iconPath[MAX_PATH];
	sprintf(iconPath, "%s%ls", WtoA(getMiscPath(PATH_ICON)), getMiscArchiveName(PATH_ICON));
	if (archiveLoad.check(iconPath) == ArchiveLoad::ARC_NONE || loadIconFromArchive(iconPath)) {
		return 0;
	}

	const char* gamename = NULL;
	NODEINFO* nodeInfo = NULL;
	int id = -1;

	map<string, NODEINFO*>::iterator iter = driverNode.begin();
	for (; iter != driverNode.end(); iter++) {
		nodeInfo = iter->second;

		// load icon
		gamename = BurnDrvGetTextA(DRV_NAME, nodeInfo->driverId);
		id = findIconMap(gamename);
		if (id >= 0) {
			nodeInfo->iconid = id;
		} else {
			// use parent's icon
			if (BurnDrvGetTextA(DRV_PARENT, nodeInfo->driverId)) {
				gamename = BurnDrvGetTextA(DRV_PARENT, nodeInfo->driverId);
				id = findIconMap(gamename);
				if (id >= 0) {
					nodeInfo->iconid = id;
				}
			}
		}
	}

	return 0;
}

static void startLoadIcon()
{
	// start icon load thread
	unsigned iconThreadId;
	iconThread = (HANDLE)_beginthreadex(NULL, 0, iconThreadEntry, NULL, 0, &iconThreadId);
	if (!iconThread) {
		return;
	}
	SetThreadPriority(iconThread, THREAD_PRIORITY_BELOW_NORMAL);
}
// <== icon load

// ==> node info, added by regret
static bool nodeInit = false;

static inline void initNodeInfo()
{
	if (nodeInit) {
		return;
	}

	unsigned drvCount = BurnGetDrvCount();
	NODEINFO* nodeInfo = NULL;
	for (unsigned i = 0; i < drvCount; i++) {
		if (BurnDrvGetFlags(i) & BDF_BOARDROM) {
			continue; // skip bios
		}
		nodeInfo = new NODEINFO;
		nodeInfo->driverId = i;
		nodeInfo->iconid = -1;
		driverNode[BurnDrvGetTextA(DRV_NAME, i)] = nodeInfo;
	}

	startLoadIcon(); // start icon load thread

	nodeInit = true;
}

static inline void resetNodeInfo()
{
	NODEINFO* nodeInfo = NULL;
	map<string, NODEINFO*>::iterator iter = driverNode.begin();

	for (; iter != driverNode.end(); iter++) {
		nodeInfo = iter->second;
		nodeInfo->used = false;
		nodeInfo->isParent = false;
		nodeInfo->hTreeHandle = NULL;
	}
}

void clearNodeInfo()
{
	if (iconThread) {
		CloseHandle(iconThread);
		iconThread = NULL;
	}

	map<string, NODEINFO*>::iterator iter = driverNode.begin();
	for (; iter != driverNode.end(); iter++) {
		delete iter->second;
	}
	driverNode.clear();

	clearIcons();

	nodeInit = false;

	// delete favorite
	delete favorites;
}
// <== node info, added by regret

// Make a tree-view control with all drivers
static int selListMake()
{
	nodeCount = 0;
	cloneCount = 0;

	if (hSelList == NULL) {
		return 1;
	}

	resetNodeInfo();

	// ==> game list filter, added by regret
	showUnAvail = false;
	if (hasFilterStr && !_wcsicmp(szFilterStr, szShowUnAvail)) {
		showUnAvail = true;
	}
	// <== game list filter

	// Add all the driver names to the list
	map<string, NODEINFO*>::iterator iter;
	NODEINFO* nodeInfo = NULL;
	map<string, NODEINFO*>::iterator iter1;
	NODEINFO* nodeInfo1 = NULL;

	// 1st: parents
	for (iter = driverNode.begin(); iter != driverNode.end(); iter++) {
		nodeInfo = iter->second;

		// ==> game list filter, added by regret
		if (gameListFilter(true, nodeInfo->driverId)) {
			continue;
		}
		// <== game list filter

		TVINSERTSTRUCT TvItem;
		memset(&TvItem, 0, sizeof(TvItem));
		TvItem.item.mask = TVIF_TEXT | TVIF_PARAM;
		TvItem.hInsertAfter = TVI_SORT;
		TvItem.item.pszText = (LPWSTR)transGameName(BurnDrvGetTextA(DRV_FULLNAME, nodeInfo->driverId), nodeInfo->driverId);
		TvItem.item.lParam = (LPARAM)nodeInfo;

		nodeInfo->hTreeHandle = TreeView_InsertItem(hSelList, &TvItem);
		nodeInfo->isParent = true;
		nodeInfo->used = true;

		nodeCount++;
	}

	// 2nd: clones
	for (iter = driverNode.begin(); iter != driverNode.end(); iter++) {
		nodeInfo = iter->second;

		// ==> game list filter, added by regret
		if (gameListFilter(false, nodeInfo->driverId)) {
			continue;
		}
		// <== game list filter

		TVINSERTSTRUCT TvItem;
		memset(&TvItem, 0, sizeof(TvItem));
		TvItem.item.mask = TVIF_TEXT | TVIF_PARAM;
		TvItem.hInsertAfter = TVI_SORT;
		TvItem.item.pszText = (LPWSTR)transGameName(BurnDrvGetTextA(DRV_FULLNAME, nodeInfo->driverId), nodeInfo->driverId);

		if (!hasFilterStr || !showUnAvail) {
			// Find the parent's handle
			map<string, NODEINFO*>::iterator iter = driverNode.find(BurnDrvGetTextA(DRV_PARENT, nodeInfo->driverId));
			if (iter != driverNode.end()) {
				if (iter->second->isParent) {
					TvItem.hParent = iter->second->hTreeHandle;
				}
			}

			// Find the parent and add a branch to the tree
			if (!TvItem.hParent) {
				unsigned i = BurnDrvGetIndexByNameA(BurnDrvGetTextA(DRV_PARENT, nodeInfo->driverId));
				if (i < BurnGetDrvCount()) {
					iter1 = driverNode.find(BurnDrvGetTextA(DRV_NAME, i));
					nodeInfo1 = iter1->second;

					TVINSERTSTRUCT TempTvItem;
					memset(&TempTvItem, 0, sizeof(TempTvItem));
					TempTvItem.item.mask = TVIF_TEXT | TVIF_PARAM;
					TempTvItem.hInsertAfter = TVI_SORT;
					TempTvItem.item.pszText = (LPWSTR)transGameName(BurnDrvGetTextA(DRV_FULLNAME, i), i);
					TempTvItem.item.lParam = (LPARAM)nodeInfo1;

					nodeInfo1->hTreeHandle = TreeView_InsertItem(hSelList, &TempTvItem);
					nodeInfo1->isParent = true;
					nodeInfo1->used = true;

//					TvItem.item.lParam = (LPARAM)nodeInfo1;
					TvItem.hParent = nodeInfo1->hTreeHandle;

					nodeCount++;
				}
			}
		}

		TvItem.item.lParam = (LPARAM)nodeInfo;
		nodeInfo->hTreeHandle = TreeView_InsertItem(hSelList, &TvItem);
		nodeInfo->used = true;

		nodeCount++;
		cloneCount++;
	}

#if 1
	for (iter = driverNode.begin(); iter != driverNode.end(); iter++) {
		// See if we need to expand the branch of an unavailable or non-working parent
		nodeInfo = iter->second;
		if (!nodeInfo->used) {
			continue;
		}

		if (nodeInfo->isParent && ((loadMenuShowX & AUTOEXPAND)
				|| auditState->get(nodeInfo->driverId) == AuditState::AUDIT_FAIL
				|| !checkWorkingStatus(nodeInfo->driverId))) {
			for (iter1 = driverNode.begin(); iter1 != driverNode.end(); iter1++) {
				// Expand the branch only if a working clone is available
				nodeInfo1 = iter1->second;
				if (!nodeInfo1->used) {
					continue;
				}

				if (auditState->get(nodeInfo->driverId) != AuditState::AUDIT_FAIL) {
					if (BurnDrvGetTextA(DRV_PARENT, nodeInfo1->driverId) && iter->first == BurnDrvGetTextA(DRV_PARENT, nodeInfo1->driverId)) {
						TreeView_Expand(hSelList, nodeInfo->hTreeHandle, TVE_EXPAND);
						break;
					}
				}
			}
		}
	}
#endif

	return 0;
}

// preview image position, added by regret
static inline void initPreviewPos()
{
	RECT rect;
	GetWindowRect(GetDlgItem(hSelDlg, IDC_SCREENSHOT_H), &rect);
	POINT pt;
	pt.x = rect.left; pt.y = rect.top;
	ScreenToClient(hSelDlg, &pt);

	previewOrgX = pt.x;
	previewOrgY = pt.y;
}

static inline void movePreviewPos()
{
	extern void getResizeOffset(int& x, int& y);
	int offsetx, offsety;
	getResizeOffset(offsetx, offsety);

	SetWindowPos(GetDlgItem(hSelDlg, IDC_SCREENSHOT_H), NULL,
			previewOrgX + offsetx, previewOrgY + offsety, 0, 0,
			SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_NOZORDER);
}

static inline void resetPreviewPos()
{
	SetWindowPos(GetDlgItem(hSelDlg, IDC_SCREENSHOT_H), NULL,
			previewOrgX, previewOrgY, 0, 0,
			SWP_NOSIZE | SWP_NOSENDCHANGING | SWP_NOZORDER);
}

static void refreshPanel()
{
	// clear preview shot
	if (hPreview) {
		DeleteObject((HGDIOBJ)hPreview);
		hPreview = NULL;
	}
	killImageTimer();

	SendDlgItemMessage(hSelDlg, IDC_SCREENSHOT_H, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hDefPreview);
	resetPreviewPos();

	CheckDlgButton(hSelDlg, IDC_CHECK7ZROMS, loadMenuShowX & CHECK7ZIP ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_CHECKAUTOEXPAND, (loadMenuShowX & AUTOEXPAND) ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_CHECKAVAILABLEONLY, (loadMenuShowX & AVAILONLY) ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_SEL_SHORTNAME, loadMenuShowX & SHOWSHORT ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_SEL_TRANSLANG, loadMenuShowX & TRANSLANG ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_CHECKCRC, loadMenuShowX & DISABLECRC ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_SEL_FAVORITE, loadMenuShowX & SHOWFAVORITE ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_NOICON, loadMenuShowX & NOICON ? BST_CHECKED : BST_UNCHECKED);
	CheckDlgButton(hSelDlg, IDC_IMAGEKEEPGAMEASPECT, loadMenuShowX & KEEPGAMEASPECT ? BST_CHECKED : BST_UNCHECKED);
};

static void rebuildEverything()
{
	refreshPanel();

	treeBuilding = true;
	SendMessage(hSelList, WM_SETREDRAW, (WPARAM)FALSE, (LPARAM)TVI_ROOT);	// disable redraw
	TreeView_DeleteItem(hSelList, TVI_ROOT);								// Destory all nodes

	hIconEffectItem = NULL;
	ShowWindow(GetDlgItem(hSelDlg, IDC_SCREENSHOT_V), SW_HIDE);

	selListMake();
	SendMessage(hSelList, WM_SETREDRAW, (WPARAM)TRUE, (LPARAM)TVI_ROOT);	// enable redraw
	treeBuilding = false;

	// set list focus
	if (dialogSelect > -1) {
		map<string, NODEINFO*>::iterator iter = driverNode.find(BurnDrvGetTextA(DRV_NAME, dialogSelect));
		TreeView_Select(hSelList, iter->second->hTreeHandle, TVGN_CARET);
		SendMessage(hSelDlg, WM_NEXTDLGCTL, (WPARAM)hSelList, TRUE);
	}

	// ==> display game count, added by regret
	wchar_t szGameCount[128];
	swprintf(szGameCount, localiseStringEx(IDS_SEL_GAMECOUNT), nodeCount, cloneCount);
	SetDlgItemText(hSelDlg, IDC_GAMECOUNT, szGameCount);
	// <== display game count
}

// get preview image path, added by regret
static inline void getImagePath(wchar_t* imgDir, unsigned index)
{
	int path = PATH_PREVIEW;
	for (int i = 0; imageDef[i].imageId >= 0; i++) {
		if (nCurImgTab == imageDef[i].imageId) {
			path = imageDef[i].pathId;
			break;
		}
	}

	wcscpy(archiveName, getMiscArchiveName(path));
	wcscpy(imgDir, getMiscPath(path));

#ifdef MD_DRIVER_LIST
	// get md image path
	if ((BurnDrvGetHardwareCode(index) & HARDWARE_PUBLIC_MASK) == HARDWARE_SEGA_MEGADRIVE) {
		wcscat(imgDir, L"MD\\"));
	}
#endif
}

static inline bool getKeepImageAspect()
{
	if (nCurImgTab == IMG_FLYER) {
		return false;
	}
	return (loadMenuShowX & KEEPGAMEASPECT);
}

// try to load image from archive file, added by regret
static HBITMAP getArchivePreviewImage(const wchar_t* szGameName, bool resize, unsigned index)
{
	wchar_t imgPath[MAX_PATH];
	getImagePath(imgPath, index); // get image path of current tab

	char arcFullName[MAX_PATH];
	sprintf(arcFullName, "%s%ls", WtoA(imgPath), archiveName);

	int ret = archiveLoad.check(arcFullName);
	if (ret == ArchiveLoad::ARC_NONE) {
		return NULL; // no archive
	}

	char szImgName[MAX_PATH];
	sprintf(szImgName, "%s.png", WtoA(szGameName));

	const char* szParentName = BurnDrvGetTextA(DRV_PARENT, index);

	void* buf = NULL;
	unsigned size = 0;
	ret = archiveLoad.loadOneFile(arcFullName, szImgName, &buf, &size);
	if (ret && szParentName) {
		// try parent
		sprintf(szImgName, "%s.png", szParentName);
		ret = archiveLoad.loadOneFile(arcFullName, szImgName, &buf, &size);
	}

	if (!ret) {
		HBITMAP hImage = loadImageFromBuffer(buf, size, hSelDlg, getKeepImageAspect());
		if (resize) {
			unsigned w, h;
			imageGetLastSize(w, h);
			hImage = resizeBitmap(hSelDlg, hImage, w, h, getKeepImageAspect(), index);
		}
		free(buf);
		return hImage;
	}
	return NULL;
}

static HBITMAP getPreviewImage(bool reset, bool resize, unsigned index)
{
	if (index >= BurnGetDrvCount()) {
		return NULL;
	}

	static int nIndex;
	int nOldIndex;

	wchar_t szBaseName[MAX_PATH];
	wchar_t filename[MAX_PATH];
	wchar_t imgPath[MAX_PATH];
	wchar_t szGameName[MAX_PATH];
	FILE* fp = NULL;

	wcscpy(szGameName, AtoW(BurnDrvGetTextA(DRV_NAME, index)));

	nOldIndex = nIndex;
	nIndex++;

	if (reset) {
		nIndex = 1;
		nOldIndex = -1;
	}

	getImagePath(imgPath, index); // get image path of current tab

	// get image name
	do {
		// Try to load a .PNG preview image
		wcscpy(szBaseName, imgPath);
		wcscat(szBaseName, szGameName);

		if (nIndex == 1) {
			swprintf(filename, L"%s.png", szBaseName);
			fp = _wfopen(filename, L"rb");
		}
		if (!fp) {
			swprintf(filename, L"%s [%02i].png", szBaseName, nIndex);
			fp = _wfopen(filename, L"rb");
		}

		// Try the parent
		if (!fp && BurnDrvGetTextA(DRV_PARENT, index)) {
			wcscpy(szBaseName, imgPath);
			wcscat(szBaseName, AtoW(BurnDrvGetTextA(DRV_PARENT, index)));
			if (nIndex == 1) {
				swprintf(filename, L"%s.png", szBaseName);
				fp = _wfopen(filename, L"rb");
			}
			if (!fp) {
				swprintf(filename, L"%s [%02i].png", szBaseName, nIndex);
				fp = _wfopen(filename, L"rb");
			}
		}

		if (nIndex == 1) {
			break;
		}

		if (!fp) {
			nIndex = 1;
		}
	} while (!fp);

	// get image handle
	HBITMAP hImage = NULL;
	if (fp && nIndex != nOldIndex) {
		hImage = loadImageFromFile(filename, hSelDlg, getKeepImageAspect());
		if (resize) {
			unsigned w, h;
			imageGetLastSize(w, h);
			hImage = resizeBitmap(hSelDlg, hImage, w, h, getKeepImageAspect(), index);
		}
		fclose(fp);
	}

	// try to get image handle from archive
	if (hImage == NULL) {
		hImage = getArchivePreviewImage(szGameName, resize, index);
	}

	return hImage;
}

static inline int updatePreview(bool reset, unsigned index)
{
	if (reset) {
		if (hPreview) {
			DeleteObject((HGDIOBJ)hPreview);
			hPreview = NULL;
		}
		killImageTimer();
	}

	HBITMAP hNewImage = getPreviewImage(reset, true, index);

	// Show the preview image
	if (hNewImage) {
		DeleteObject((HGDIOBJ)hPreview);
		hPreview = hNewImage;

		SendDlgItemMessage(hSelDlg, IDC_SCREENSHOT_H, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hPreview);
		movePreviewPos();

		imageTimer = SetTimer(hSelDlg, IMAGE_TIMER_ID, IMAGE_TIMER, NULL);
	} else {
		// We couldn't load a new image for this game, so kill the timer (it will be restarted when a new game is selected)
		killImageTimer();

		if (!hPreview) {
			SendDlgItemMessage(hSelDlg, IDC_SCREENSHOT_H, STM_SETIMAGE, IMAGE_BITMAP, (LPARAM)hDefPreview);
			resetPreviewPos();
		}
	}

	return 0;
}

// ==> tab control, added by regret
static int setSelTab(int tab)
{
	if (MAX_TAB < tab)
		return -1;

	return TabCtrl_SetCurSel(hSelTab, tab);
}

static void showOptionControls(int tabIndex, bool show)
{
	int flag = show ? SW_SHOW : SW_HIDE;

	switch (tabIndex) {
		case LIST:
			ShowWindow(GetDlgItem(hSelDlg, IDC_SEL_SHORTNAME), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_SEL_TRANSLANG), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_NOICON), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_IMAGEKEEPGAMEASPECT), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_CHECK7ZROMS), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_CHECKCRC), flag);
			break;

		case FILTER:
			ShowWindow(GetDlgItem(hSelDlg, IDC_CHECKAVAILABLEONLY), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_CHECKAUTOEXPAND), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_SEL_FAVORITE), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_FILTER_KEY), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_FILTER_SYSTEM), flag);
			ShowWindow(GetDlgItem(hSelDlg, IDC_DRIVERCONFIG), flag);
			break;

//		case ETC:
//			break;

		default:
			break;
	}
}

static void setOptionTab(int nTab)
{
	// Hide all controls
	if (nTab == MAX_TAB) {
		showOptionControls(LIST, false);
		showOptionControls(FILTER, false);
		return;
	}

	if (nCurrentTab == nTab || MAX_TAB < nTab)
		return;

	// Hide controls
	showOptionControls(nCurrentTab, false);

	// Show new controls
	nCurrentTab = nTab;
	loadTabSel = nCurrentTab;
	showOptionControls(nCurrentTab, true);
}

static inline void updateBackgroundBrush(HWND hWndTab)
{
	// Check if the application is themed
	if (fnIsThemed) {
		themeActive = fnIsThemed();
	}
	// Destroy old brush
	if (hTabBkBrush) {
		DeleteObject(hTabBkBrush);
		hTabBkBrush = NULL;
	}

	// Only do this if the theme is active
	if (themeActive) {
		RECT rc;
		HDC hDC, hDCMem;
		HBITMAP hBmp, hBmpOld;

		// Get tab control dimensions
		GetWindowRect(hWndTab, &rc);

		// Get the tab control DC
		hDC = GetDC(hWndTab);

		// Create a compatible DC
		hDCMem = CreateCompatibleDC(hDC);
		hBmp = CreateCompatibleBitmap(hDC, rc.right-rc.left, rc.bottom-rc.top);
		hBmpOld = (HBITMAP)(SelectObject(hDCMem, hBmp));

		// Tell the tab control to paint in our DC
		SendMessage(hWndTab, WM_PRINTCLIENT, (WPARAM)(hDCMem),
			(LPARAM)(PRF_ERASEBKGND | PRF_CLIENT | PRF_NONCLIENT));

		// Create a pattern brush from the bitmap selected in our DC
		hTabBkBrush = CreatePatternBrush(hBmp);

		// Restore the bitmap
		SelectObject(hDCMem, hBmpOld);

		// Cleanup
		DeleteObject(hBmp);
		DeleteDC(hDCMem);
		ReleaseDC(hWndTab, hDC);
	}
}

static void initOptionTab()
{
	hSelTab = GetDlgItem(hSelDlg, IDC_SEL_TAB);
	nCurrentTab = NONE;

	HMODULE hThemes = LoadLibrary(L"uxtheme.dll");
	if (hThemes) {
		fnIsThemed = GetProcAddress(hThemes, "IsAppThemed");
		FreeLibrary(hThemes);

		updateBackgroundBrush(hSelTab);
	}
	themeActive = false;

	// Clear all to be sure
	TabCtrl_DeleteAllItems(hSelTab);

	// init tab items
	TCITEM tie;
	memset(&tie, 0, sizeof(tie));
	tie.mask = TCIF_TEXT;

	for (int i = 0; i < MAX_TAB; i++) {
		tie.pszText = localiseStringEx(IDS_SELTAB_LIST + i);
		TabCtrl_InsertItem(hSelTab, i, &tie);
	}

	setOptionTab(MAX_TAB); // hide all controls
}

static void setImgTab(int nTab)
{
	if (nTab >= IMG_MAXTAB)
		return;

	nCurImgTab = nTab;

	if (dialogSelect >= 0) {
		updatePreview(true, dialogSelect);
	}
}

static int getNextImgTab()
{
	if (nCurImgTab + 1 >= IMG_MAXTAB) {
		return IMG_NONE + 1;
	} else {
		return nCurImgTab + 1;
	}
}

static void initImageTab()
{
	hImgTab = GetDlgItem(hSelDlg, IDC_IMAGE_TAB);

	// Clear all to be sure
	TabCtrl_DeleteAllItems(hImgTab);

	// init tab items
	TCITEM tie;
	memset(&tie, 0, sizeof(tie));
	tie.mask = TCIF_TEXT;

	for (int i = 0; i < IMG_MAXTAB; i++) {
		tie.pszText = localiseStringEx(IDS_IMAGETAB_PREVIEW + i);
		TabCtrl_InsertItem(hImgTab, i, &tie);
	}

	TabCtrl_SetCurSel(hImgTab, nCurImgTab);
}
// <== tab control

static void displayRomInfo()
{
	char* romName;
	struct BurnRomInfo ri;
	wchar_t strTemp[256];

	FBAPopupAddText(PUF_TYPE_INFO, L"romset info for %hs : \n", BurnDrvGetTextA(DRV_NAME, dialogSelect));
	FBAPopupAddText(PUF_TYPE_INFO, L"format: rom name, size(in bytes), crc(hex) \n\n");

	for (int i = 0; !BurnDrvGetRomName(&romName, i, 0, dialogSelect); i++) {
		BurnDrvGetRomInfo(&ri, i, dialogSelect);

		if (ri.nLen == 0 && ri.nCrc == 0) break;

		swprintf(strTemp, L"%hs, %d, %x\n", romName, ri.nLen, ri.nCrc);

		FBAPopupAddText(PUF_TYPE_INFO, strTemp);
	}

	FBAPopupDisplay(PUF_TYPE_INFO);
}

static inline void onNodeItemClick(HTREEITEM item, int iconid)
{
	if ((loadMenuShowX & NOICON) || !item) {
		return;
	}

	if (iconid < 0) {
		hIconEffectItem = NULL;
		ShowWindow(GetDlgItem(hSelDlg, IDC_SCREENSHOT_V), SW_HIDE);
		return; // only recover state
	}
	hIconEffectItem = item;
	ShowWindow(GetDlgItem(hSelDlg, IDC_SCREENSHOT_V), SW_SHOW);

	InvalidateLargeIcon();
}

static BOOL selContextMenuProc()
{
	// get mouse position
	POINT pt, menuPt;
	GetCursorPos(&pt);
	ScreenToClient(hSelList, &pt);
	GetCursorPos(&menuPt);

	TVHITTESTINFO lpht;
	memset(&lpht, 0, sizeof(lpht));
	lpht.pt.x = pt.x;
	lpht.pt.y = pt.y;

	// get driver number
	HTREEITEM hSelectHandle = TreeView_HitTest(hSelList, (LPARAM)(&lpht));
	if (!hSelectHandle) {		// Nothing is selected, return without closing the window
		return FALSE;
	}
	TreeView_SelectItem(hSelList, hSelectHandle);

	map<string, NODEINFO*>::iterator iter = driverNode.begin();
	for (; iter != driverNode.end(); iter++) {
		if (iter->second->used && hSelectHandle == iter->second->hTreeHandle) {
			dialogSelect = iter->second->driverId;
			break;
		}
	}

	HMENU hContextMenu = GetSubMenu(hMenuSelPop, 0);

#ifndef NO_IPS
	// IPS config menu item
	if (patcher.getCount(dialogSelect) > 0) {
		EnableMenuItem(hContextMenu, MENU_IPSSETTING, MF_ENABLED | MF_BYCOMMAND);
	} else {
		EnableMenuItem(hContextMenu, MENU_IPSSETTING, MF_GRAYED | MF_BYCOMMAND);
	}
#endif
	if (loadMenuShowX & SHOWFAVORITE) {
		EnableMenuItem(hContextMenu, MENU_ADD_FAVORITE, MF_GRAYED | MF_BYCOMMAND);
		EnableMenuItem(hContextMenu, MENU_REMOVE_FAVORITE, MF_ENABLED | MF_BYCOMMAND);
	}
	else {
		EnableMenuItem(hContextMenu, MENU_ADD_FAVORITE, MF_ENABLED | MF_BYCOMMAND);
		EnableMenuItem(hContextMenu, MENU_REMOVE_FAVORITE, MF_GRAYED | MF_BYCOMMAND);
	}

#ifndef NO_IMAGEMENU
	if (menuNewStyle) {
		createImageMenu(hSelDlg, hMenuSelPop);
	}
#endif
	TrackPopupMenuEx(hContextMenu, TPM_LEFTALIGN | TPM_RIGHTBUTTON, menuPt.x, menuPt.y, hSelDlg, NULL);

	return TRUE;
}

static void initSelDialog()
{
	initNodeInfo(); // init node info

	// ==> load custom resource, modified by regret
	loadCustomRes();
	// <== load custom resource

	// ==> tab control, added by regret
	initOptionTab(); // initialize tab control
	setOptionTab(loadTabSel);
	setSelTab(loadTabSel);

	initImageTab();
	// <== tab control

	// ==> game list filter, added by regret
	initFilterString();
	initFilterSystem();
	// <== game list filter

	// ==> favorite filter, added by regret
	if (!favorites) {
		favorites = new Favorites();
		favorites->init();
	}
	// <== favorite filter

	initPreviewPos();

	dialogSelect = lastSelect;
	if (dialogSelect < 0) {
		dialogSelect = 0;
	}

#ifndef NO_IPS
	patcher.exit();
#endif
}

static INT_PTR CALLBACK selDialogProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM lParam)
{
	if (msg == WM_INITDIALOG) {

		wchar_t szOldTitle[256];
		wchar_t szNewTitle[512];

		GetWindowText(hDlg, szOldTitle, sizearray(szOldTitle));
		_snwprintf(szNewTitle, sizearray(szOldTitle), APP_TITLE SEPERATOR_1 L"%s", szOldTitle);
		SetWindowText(hDlg, szNewTitle);

		hSelDlg = hDlg;
		hSelList = GetDlgItem(hDlg, IDC_SELTREE1);

		for (int i = 0; i < INFO_NUM; i++) {
			hInfoLabel[i] = GetDlgItem(hSelDlg, infoDef[i].label);
			hInfoText[i] = GetDlgItem(hSelDlg, infoDef[i].text);
		}

		initSelDialog();

		rebuildEverything();
		return TRUE;
	}

	if (msg == WM_COMMAND) {
		if (HIWORD(wParam) == BN_CLICKED) {
			int wID = LOWORD(wParam);
			switch (wID) {
				case IDOK:
					selOkay();
					break;
				case IDC_DRIVERCONFIG:
					driverConfigDialog(hDlg);
					break;
				case IDCANCEL:
					SendMessage(hDlg, WM_CLOSE, 0, 0);
					return FALSE;

				case IDC_CHECK7ZROMS:
					loadMenuShowX ^= CHECK7ZIP;
					break;
				case IDC_CHECKCRC:
					loadMenuShowX ^= DISABLECRC;
					break;

				case IDC_IMAGEKEEPGAMEASPECT:
					loadMenuShowX ^= KEEPGAMEASPECT;
					updatePreview(true, dialogSelect);
					break;
				case IDC_NOICON:
					loadMenuShowX ^= NOICON;
					rebuildEverything();
					break;
				case IDC_CHECKAVAILABLEONLY:
					loadMenuShowX ^= AVAILONLY;
					rebuildEverything();
					break;
				case IDC_CHECKAUTOEXPAND:
					loadMenuShowX ^= AUTOEXPAND;
					rebuildEverything();
					break;
				case IDC_SEL_SHORTNAME:
					loadMenuShowX ^= SHOWSHORT;
					rebuildEverything();
					break;
				case IDC_SEL_FAVORITE:
					loadMenuShowX ^= SHOWFAVORITE;
					rebuildEverything();
					break;
				/* modify by Sho, thanks 800 */
				case IDC_SEL_TRANSLANG:
					loadMenuShowX ^= TRANSLANG;
					rebuildEverything();
					break;
				/* end Sho, thanks 800 */
			}
		}

		int id = LOWORD(wParam);
		switch (id) {
			case MENU_PLAY_GAME:
				rightClick = true; // play game through context menu
				selOkay();
				break;

			case MENU_MAWS_INFO: {
				wchar_t szRomURL[MAX_PATH] = L"";
				swprintf(szRomURL, L"%s%hs", L"http://maws.mameworld.info/maws/set/", BurnDrvGetTextA(DRV_NAME));
				ShellExecute(NULL, L"open", szRomURL, NULL, NULL, SW_SHOWNORMAL);
				break;
			}

			case MENU_ROM_INFO: {
				displayRomInfo();
				break;
			}

#ifndef NO_IPS
			case MENU_IPSSETTING:
				patchSettingCreate(hDlg, dialogSelect);
				break;
#endif

			case MENU_ADD_FAVORITE:
				favorites->add(dialogSelect);
				break;

			case MENU_REMOVE_FAVORITE:
				favorites->remove(dialogSelect);
				rebuildEverything();
				break;
		}

		// ==> tab control, added by regret
		if (id == IDC_IMGFRAME) {
			int nNextImgTab = getNextImgTab();
			TabCtrl_SetCurSel(hImgTab, nNextImgTab);
			setImgTab(nNextImgTab);
		}
		// <==> tab control

		// ==> game list filter, added by regret
		if ((HWND)lParam == GetDlgItem(hDlg, IDC_FILTER_KEY)) {
			if (HIWORD(wParam) == CBN_SELCHANGE) {
				int iSel = SendDlgItemMessage(hDlg, IDC_FILTER_KEY, CB_GETCURSEL, 0, 0);
				SendDlgItemMessage(hDlg, IDC_FILTER_KEY, CB_GETLBTEXT, (WPARAM)iSel, (LPARAM)szFilterStr);
				filterChange();
			}
			if (HIWORD(wParam) == CBN_EDITCHANGE) {
				filterTimer = SetTimer(hSelDlg, FILTER_TIMER_ID, FILTER_TIMER, NULL);
			}
		}
		else if ((HWND)lParam == GetDlgItem(hDlg, IDC_FILTER_SYSTEM)) {
			if (HIWORD(wParam) == CBN_SELCHANGE) {
				loadSystemSel = SendDlgItemMessage(hDlg, IDC_FILTER_SYSTEM, CB_GETCURSEL, 0, 0);
				rebuildEverything();
			}
		}
		// <== game list filter
	}

	if (msg == WM_CLOSE) {
		myEndDialog(false);
		DeleteObject(whiteBGBrush);
		DeleteObject(hTabBkBrush);

		DestroyWindow(hSelDlg);
		dialogManager.remove(IDD_SELNEW);
		return FALSE;
	}

	if (msg == WM_DESTROY) {
		hSelDlg = NULL;
		hSelList = NULL;
		hIconEffectItem = NULL;
		return 0;
	}

	if (msg == WM_TIMER) {
		if ((UINT_PTR)wParam == imageTimer) {
			updatePreview(false, dialogSelect);
		}
		// ==> game list filter, added by regret
		else if((UINT_PTR)wParam == filterTimer) {
			killFilterTimer();
			SendDlgItemMessage(hDlg, IDC_FILTER_KEY, WM_GETTEXT, (WPARAM)sizearray(szFilterStr), (LPARAM)szFilterStr);
			filterChange();
		}
		// <== game list filter
		return FALSE;
	}

	if (msg == WM_CTLCOLORSTATIC) {
		for (int i = 0; i < INFO_NUM; i++) {
			if ((HWND)lParam == hInfoLabel[i]) {
				return (BOOL)whiteBGBrush;
			}
			if ((HWND)lParam == hInfoText[i]) {
				return (BOOL)whiteBGBrush;
			}
		}

		// ==> tab control, added by regret
		if ((HWND)lParam == GetDlgItem(hSelDlg, IDC_GAMECOUNT)) {
			return (BOOL)whiteBGBrush;
		} else {
			if (fnIsThemed && fnIsThemed()) {
				RECT rc;

				// Set the background mode to transparent
				SetBkMode((HDC)wParam, TRANSPARENT);

				// Get the controls window dimensions
				GetWindowRect((HWND)lParam, &rc);

				// Map the coordinates to coordinates with the upper left corner of dialog control as base
				MapWindowPoints(NULL, hSelTab, (LPPOINT)(&rc), 2);

				// Adjust the position of the brush for this control (else we see the top left of the brush as background)
				SetBrushOrgEx((HDC)wParam, -rc.left, -rc.top, NULL);

				// Return the brush
				return (INT_PTR)(hTabBkBrush);
			} else {
				SetBkColor((HDC)wParam, GetSysColor(COLOR_3DFACE));
			}
		}
		// <== tab control
	}

	if (msg == WM_NOTIFY) {
		LPNMHDR lpnmh = (LPNMHDR)lParam;

		if (lpnmh->code == NM_DBLCLK && LOWORD(wParam) == IDC_SELTREE1) {
			selOkay();

			// disable double-click node-expand
			SetWindowLongPtr(hSelDlg, DWLP_MSGRESULT, 1);
			return TRUE;
		}

		// F5 to refresh romsets
		if (lpnmh->code == TVN_KEYDOWN && !treeBuilding && LOWORD(wParam) == IDC_SELTREE1) {
			NMTVKEYDOWN* ptvkd = (LPNMTVKEYDOWN)lParam;
			if (ptvkd->wVKey == VK_F5 && !rescanRoms) {
				rescanRoms = true;
				createROMInfo();
				rebuildEverything();
				return TRUE;
			}
			return FALSE;
		}

		// ==> tab control, added by regret
		if (lpnmh->code == TCN_SELCHANGE) {
			if (lpnmh->hwndFrom == hSelTab) {
				int iTab = TabCtrl_GetCurSel(hSelTab);
				setOptionTab(iTab);
			}
			else if(lpnmh->hwndFrom == hImgTab) {
				int iImgTab = TabCtrl_GetCurSel(hImgTab);
				setImgTab(iImgTab);
			}
		}
		// <== tab control

		if (lpnmh->code == NM_RCLICK && LOWORD(wParam) == IDC_SELTREE1) {
			return selContextMenuProc();
		}

		if (lpnmh->code == NM_CUSTOMDRAW && LOWORD(wParam) == IDC_SELTREE1) {
			LPNMLVCUSTOMDRAW lplvcd = (LPNMLVCUSTOMDRAW)lParam;

			switch (lplvcd->nmcd.dwDrawStage) {
				case CDDS_PREPAINT: {
					SetWindowLongPtr(hSelDlg, DWLP_MSGRESULT, CDRF_NOTIFYITEMDRAW);
					return TRUE;
				}

				case CDDS_ITEMPREPAINT:	{
					if (lplvcd->nmcd.rc.top == lplvcd->nmcd.rc.bottom) {
						return TRUE;
					}

					HTREEITEM hSelectHandle = (HTREEITEM)(lplvcd->nmcd.dwItemSpec);
					TVITEMEX TvItem;
					TvItem.hItem = hSelectHandle;
					TvItem.mask = TVIF_PARAM | TVIF_CHILDREN | TVIF_INTEGRAL;
					TreeView_GetItem(hSelList, &TvItem);

					static RECT rect;
					rect.left = lplvcd->nmcd.rc.left;
					rect.right = lplvcd->nmcd.rc.right;
					rect.top = lplvcd->nmcd.rc.top;
					rect.bottom = lplvcd->nmcd.rc.bottom;

					const NODEINFO* node = (NODEINFO*)TvItem.lParam;

					// Set the foreground and background colours unless the item is highlighted
					if (loadMenuShowX & NOICON) {
						if (!(TvItem.state & (TVIS_SELECTED | TVIS_DROPHILITED))) {
							// Set less contrasting colours for clones
							if (!node->isParent) {
//								lplvcd->clrTextBk = RGB(0xF7, 0xF7, 0xF7);
								lplvcd->clrText = RGB(0x3F, 0x3F, 0x3F);
							}
							// Miss sets
							if (auditState->get(node->driverId) == AuditState::AUDIT_FAIL) {
								lplvcd->clrTextBk = RGB(0xEC, 0xEC, 0xEC);
							}
						}
					}

					// Not working games, change the text colour
					if (!checkWorkingStatus(node->driverId)) {
						lplvcd->clrText = RGB(0x80, 0x00, 0x00);
					}

					{
						// Fill background
						HBRUSH hBackBrush = CreateSolidBrush(lplvcd->clrTextBk);
						FillRect(lplvcd->nmcd.hdc, &lplvcd->nmcd.rc, hBackBrush);
						DeleteObject(hBackBrush);
					}

					{
						// Draw icons if needed
						drawIcons(TvItem, rect, lplvcd->nmcd.hdc, node->driverId);

						// Draw text
						SetTextColor(lplvcd->nmcd.hdc, lplvcd->clrText);
						SetBkMode(lplvcd->nmcd.hdc, TRANSPARENT);

						const wchar_t* pszName = transGameName(BurnDrvGetTextA(DRV_FULLNAME, node->driverId), node->driverId);

						SIZE size = { 0, 0 };
						GetTextExtentPoint32(lplvcd->nmcd.hdc, pszName, (int)wcslen(pszName), &size);
						DrawText(lplvcd->nmcd.hdc, pszName, -1, &rect, DT_NOPREFIX | DT_SINGLELINE | DT_LEFT | DT_VCENTER);
					}

					SetWindowLongPtr(hSelDlg, DWLP_MSGRESULT, CDRF_SKIPDEFAULT);
					return TRUE;
				}

				default: {
					SetWindowLongPtr(hSelDlg, DWLP_MSGRESULT, CDRF_DODEFAULT);
					return TRUE;
				}
			}
		}

		if (lpnmh->code == TVN_ITEMEXPANDING && !treeBuilding && LOWORD(wParam) == IDC_SELTREE1) {
			TreeView_Select(hSelList, ((LPNMTREEVIEW)lParam)->itemNew.hItem, TVGN_CARET);
			return FALSE;
		}

		if (lpnmh->code == TVN_SELCHANGED && !treeBuilding && LOWORD(wParam) == IDC_SELTREE1) {
			HTREEITEM hSelectHandle = TreeView_GetNextItem(hSelList, ~0U, TVGN_CARET);

			// Search through driverNode[] for the driverId according to the returned hSelectHandle
			map<string, NODEINFO*>::iterator iter = driverNode.begin();
			for (; iter != driverNode.end(); iter++) {
				if (iter->second->used && hSelectHandle == iter->second->hTreeHandle) {
					dialogSelect = iter->second->driverId;

					// icon effect
					onNodeItemClick(hSelectHandle, iter->second->iconid);
					break;
				}
			}

			wchar_t szItemText[1024];

			// Get the text from the drivers via BurnDrvGetText()
			for (int i = 0; i < INFO_NUM; i++) {
				bool useInfo = true;

				switch (i) {
					case INFO_ROMNAME: {
						swprintf(szItemText, L"%hs", BurnDrvGetTextA(DRV_NAME, dialogSelect));
						if ((BurnDrvGetFlags(dialogSelect) & BDF_CLONE) && BurnDrvGetTextA(DRV_PARENT, dialogSelect)) {
							swprintf(szItemText + wcslen(szItemText), L" (clone of %hs)", BurnDrvGetTextA(DRV_PARENT, dialogSelect));
						}
						break;
					}
					case INFO_ROMINFO: {
						swprintf(szItemText, L""); // clear rom name
						useInfo = false;

						if (BurnDrvGetFlags(dialogSelect) & BDF_PROTOTYPE) {
							swprintf(szItemText + wcslen(szItemText), L"prototype");
							useInfo = true;
						}
						if (BurnDrvGetFlags(dialogSelect) & BDF_BOOTLEG) {
							swprintf(szItemText + wcslen(szItemText), L"%sbootleg", useInfo ? L", " : L"");
							useInfo = true;
						}

						swprintf(szItemText + wcslen(szItemText), L"%s%i player%s",
								useInfo ? L", " : L"", BurnDrvGetMaxPlayers(dialogSelect),
								(BurnDrvGetMaxPlayers(dialogSelect) != 1) ? L"s max" : L"");
						useInfo = true;

						if (BurnDrvGetTextA(DRV_BOARDROM, dialogSelect)) {
							swprintf(szItemText + wcslen(szItemText), L"%suses board-ROMs from %hs",
									useInfo ? L", " : L"", BurnDrvGetTextA(DRV_BOARDROM, dialogSelect));
						}
						break;
					}
					case INFO_SYSTEM: {
						unsigned width, height;
						BurnDrvGetVisibleSize(width, height, false, dialogSelect);

						swprintf(szItemText, L"%s (%hs, %hs, %dx%d)",
							BurnDrvGetTextA(DRV_MANUFACTURER, dialogSelect) ? AtoW(BurnDrvGetTextA(DRV_MANUFACTURER, dialogSelect)) : localiseStringEx(IDS_ERR_UNKNOWN),
							BurnDrvGetTextA(DRV_DATE, dialogSelect), BurnDrvGetTextA(DRV_SYSTEM, dialogSelect), width, height);
						break;
					}
					case INFO_COMMENT: {
						const wchar_t* pszName = transGameName(BurnDrvGetTextA(DRV_FULLNAME, dialogSelect), dialogSelect);
						_snwprintf(szItemText, sizearray(szItemText), pszName);
						if (!szItemText[0]) {
							useInfo = false;
						}
						break;
					}
					case INFO_NOTE: {
						const char* pszTmp = BurnDrvGetTextA(DRV_COMMENT, dialogSelect);
						if (!pszTmp || !pszTmp[0]) useInfo = false;
						swprintf(szItemText, L"%hs", useInfo ? BurnDrvGetTextA(DRV_COMMENT, dialogSelect) : "");
						break;
					}
				}
				// set game info text
				SendMessage(hInfoText[i], WM_SETTEXT, 0, (LPARAM)szItemText);
				EnableWindow(hInfoLabel[i], useInfo);
			}

			updatePreview(true, dialogSelect);
		}
	}

	return FALSE;
}

int selDialog(HWND parent)
{
	if (hSelDlg) {
		// already open so just reactivate the window
		SetActiveWindow(hSelDlg);
		return 0;
	}

	hSelDlg = localiseCreateDialog(IDD_SELNEW, parent, (DLGPROC)selDialogProc);
	if (!hSelDlg) {
		return 1;
	}
	dialogManager.add(IDD_SELNEW, hSelDlg);

	wndInMid(hSelDlg, parent);
	ShowWindow(hSelDlg, SW_NORMAL);
	return 0;
}


// driver config dialog, added by regret
HWND hDriverDlg = NULL;

static void refreshDriverConfig()
{
	if (!hDriverDlg) {
		return;
	}

	for (int i = 0; systemDef[i].buttonCode; i++) {
		if (systemDef[i].buttonCode > 0) {
			CheckDlgButton(hDriverDlg, systemDef[i].buttonCode,
				(loadDriverShowX & systemDef[i].hardware) ? BST_UNCHECKED : BST_CHECKED);
		}
	}
}

static void setDriverConfig(const int& id)
{
	for (int i = 0; systemDef[i].buttonCode; i++) {
		if (systemDef[i].buttonCode > 0 && systemDef[i].buttonCode == id) {
			loadDriverShowX ^= systemDef[i].hardware;
			break;
		}
	}
}

static INT_PTR CALLBACK driverConfigProc(HWND hDlg, UINT msg, WPARAM wParam, LPARAM)
{
	if (msg ==  WM_INITDIALOG) {
		hDriverDlg = hDlg;
		wndInMid(hDlg, hSelDlg ? hSelDlg : hScrnWnd);

		refreshDriverConfig();
		return TRUE;
	}
	else if (msg ==  WM_COMMAND) {
		if (HIWORD(wParam) == BN_CLICKED) {
			int id = LOWORD(wParam);
			switch (id) {
				case IDOK:
				case IDCANCEL:
					SendMessage(hDlg, WM_CLOSE, 0, 0);
					break;

				case IDC_DRIVERALL:
					loadDriverShowX = 0;
					refreshDriverConfig();
					break;
				case IDC_DRIVERNONE:
					loadDriverShowX = MASKALL;
					refreshDriverConfig();
					break;

				default:
					setDriverConfig(id);
					break;
			}
		}
	}
	else if (msg ==  WM_CLOSE) {
		EndDialog(hDlg, 0);
	}

	return FALSE;
}

int driverConfigDialog(HWND parent)
{
	localiseDialogBox(IDD_DRIVER, parent, (DLGPROC)driverConfigProc);
	hDriverDlg = NULL;

	rebuildEverything();

	return 0;
}
